/*
 * Copyright (c) 2006-2021, Ulandlink Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-08-18     lfz         first version
 */

#include "drv_spi.h"

#ifdef RT_USING_SPI
#include "main.h"
#include "os_types.h"
#include <spi.h>

#ifdef RT_USING_PIN
#include <pin.h>
#endif

#define RT_USING_SPI0
//#define RT_USING_SPI1
//#define RT_USING_SPI2
//#define RT_USING_SPI3
//#define RT_USING_SPI4

#define ARR_LEN(__N)      (sizeof(__N) / sizeof(__N[0]))
	
//#define DEBUG
#ifdef DEBUG
#define DEBUG_PRINTF(...)   rt_kprintf(__VA_ARGS__)
#else
#define DEBUG_PRINTF(...)
#endif

/**
 * retry process
 *
 * @param delay delay function for every retry. NULL will not delay for every retry.
 * @param retry retry counts
 * @param result FALSE: retry timeout   		
 */
#define SPI_RETRY_PROCESS(delay, retry, result)         \
{														\
void (*__delay_temp)(void) = (void (*)(void))delay; 	\
if (retry == 0){										\
	result = FALSE;										\
	break;												\
} else {												\
	if (__delay_temp) {									\
		__delay_temp();									\
	}													\
	retry --;											\
}														\
}

struct gd32f4_spi
{
	uint32_t 					 spi_periph;
	rcu_periph_enum 	 		 spi_clk;
	const char 	      			*bus_name;
	struct rt_spi_bus 			*spi_bus;
};

struct gd32_spi_cs
{
#ifdef RT_USING_PIN
	rt_base_t pin;
#else
	uint32_t GPIOx;
	uint32_t GPIO_Pin;	
#endif
};
		
static rt_err_t gd32_spi_port_init(const struct gd32f4_spi *f4_spi)
{
	RT_ASSERT(f4_spi != RT_NULL);
	uint32_t spi_periph = f4_spi->spi_periph;
	
	if (spi_periph == SPI0){
		/* SPI0_CLK(PB3), SPI0_MISO(PB4), SPI0_MOSI(PB5) GPIO pin configuration AF5*/
		rcu_periph_clock_enable(RCU_GPIOB);
		gpio_af_set(GPIOB, GPIO_AF_5, GPIO_PIN_3|GPIO_PIN_4| GPIO_PIN_5);
		gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3|GPIO_PIN_4| GPIO_PIN_5);
		gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3|GPIO_PIN_4| GPIO_PIN_5);
	} 
//	else if (spi_periph == SPI1){
//		/* SPI1_CLK(PB13), SPI1_MISO(PB14), SPI1_MOSI(PB15) GPIO pin configuration */
//		rcu_periph_clock_enable(RCU_GPIOB);
//		gpio_af_set(GPIOB, GPIO_AF_5, GPIO_PIN_13|GPIO_PIN_14| GPIO_PIN_15);
//		gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13|GPIO_PIN_14| GPIO_PIN_15);
//		gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13|GPIO_PIN_14| GPIO_PIN_15);
//	} else if (spi_periph == SPI2){
//		/* SPI2_CLK(PC10), SPI2_MISO(PC11), SPI2_MOSI(PC12) GPIO pin configuration */
//		rcu_periph_clock_enable(RCU_GPIOC);
//		gpio_af_set(GPIOC, GPIO_AF_6, GPIO_PIN_10|GPIO_PIN_11| GPIO_PIN_12);
//		gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10|GPIO_PIN_11| GPIO_PIN_12);
//		gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10|GPIO_PIN_11| GPIO_PIN_12);
//	} else if (spi_periph == SPI3){
//		/* SPI3_CLK(PE2), SPI3_MISO(PE5), SPI3_MOSI(PE6) GPIO pin configuration */
//		rcu_periph_clock_enable(RCU_GPIOE);
//		gpio_af_set(GPIOE, GPIO_AF_5, GPIO_PIN_2|GPIO_PIN_5| GPIO_PIN_6);
//		gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_2|GPIO_PIN_5| GPIO_PIN_6);
//		gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2|GPIO_PIN_5| GPIO_PIN_6);
//	} else if (spi_periph == SPI4){
//	  /* SPI4_CLK(PE12), SPI4_MISO(PE13), SPI4_MOSI(PE14) GPIO pin configuration */
//		rcu_periph_clock_enable(RCU_GPIOE);
//		gpio_af_set(GPIOE, GPIO_AF_6, GPIO_PIN_12|GPIO_PIN_13| GPIO_PIN_14);
//		gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_12|GPIO_PIN_13| GPIO_PIN_14);
//		gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_12|GPIO_PIN_13| GPIO_PIN_14);	
//	} 
	else {
		return RT_EIO;
	}
	return RT_EOK;
}


/* private rt-thread spi ops function */
static rt_err_t gd32_spi_configure(struct rt_spi_device* device, struct rt_spi_configuration* configuration)
{
	RT_ASSERT(device != RT_NULL);
	RT_ASSERT(configuration != RT_NULL);
	struct rt_spi_bus * spi_bus = (struct rt_spi_bus *)device->bus;
	struct gd32f4_spi *f4_spi = (struct gd32f4_spi *)spi_bus->parent.user_data;
	spi_parameter_struct spi_init_struct = {0};
	uint32_t spi_periph = f4_spi->spi_periph;
	
	/* init spi port */
	if ( gd32_spi_port_init(f4_spi) != RT_EOK){
		return RT_EIO;
	}
	
	/* enable clock */
	rcu_periph_clock_enable(f4_spi->spi_clk);
	
	/* data_width */
	if (configuration->data_width <= 8){
		spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
	} else if(configuration->data_width <= 16) {
		spi_init_struct.frame_size = SPI_FRAMESIZE_16BIT;
	} else {
		return RT_EIO;
	}

	/* baudrate */
	{
		rcu_clock_freq_enum spi_src;
		uint32_t spi_apb_clock;
		uint32_t max_hz;

		max_hz = configuration->max_hz;

		DEBUG_PRINTF("max   freq: %d\n", max_hz);

		if (spi_periph == SPI1 || spi_periph == SPI2) {
			spi_src = CK_APB1;
		} else {
			spi_src = CK_APB2;
		}
		spi_apb_clock = rcu_clock_freq_get(spi_src);

		if (max_hz >= spi_apb_clock >> 1){
			spi_init_struct.prescale = SPI_PSC_2;
		} else if (max_hz >= spi_apb_clock >> 2) {
			spi_init_struct.prescale = SPI_PSC_4;
		} else if (max_hz >= spi_apb_clock >> 3) {
			spi_init_struct.prescale = SPI_PSC_8;
		} else if (max_hz >= spi_apb_clock >> 4) {
			spi_init_struct.prescale = SPI_PSC_16;
		} else if (max_hz >= spi_apb_clock >> 5){
			spi_init_struct.prescale = SPI_PSC_32;
		} else if (max_hz >= spi_apb_clock >> 6){
			spi_init_struct.prescale = SPI_PSC_64;
		} else if (max_hz >= spi_apb_clock >> 7) {
			spi_init_struct.prescale = SPI_PSC_128;
		} else {			
			spi_init_struct.prescale = SPI_PSC_256;		/*  min prescaler 256 */
		}
	} /* baudrate */

	switch(configuration->mode & RT_SPI_MODE_3) {
	case RT_SPI_MODE_0:
		spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
	break;
	case RT_SPI_MODE_1:
		spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_2EDGE;
	break;
	case RT_SPI_MODE_2:
		spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_1EDGE;
	break;
	case RT_SPI_MODE_3:
		spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE;
	break;
	}

	/* MSB or LSB */
	if (configuration->mode & RT_SPI_MSB){
		spi_init_struct.endian = SPI_ENDIAN_MSB;
	} else {
		spi_init_struct.endian = SPI_ENDIAN_LSB;
	}

	spi_init_struct.trans_mode 	= SPI_TRANSMODE_FULLDUPLEX;
	spi_init_struct.device_mode = SPI_MASTER;
	spi_init_struct.nss 		= SPI_NSS_SOFT;

	spi_crc_off(spi_periph);

	/* init SPI */
	spi_init(spi_periph, &spi_init_struct);
	/* Enable SPI_MASTER */
	spi_enable(spi_periph);

	return RT_EOK;
}

static rt_uint32_t gd32_spi_xfer(struct rt_spi_device* device, struct rt_spi_message* message)
{
	RT_ASSERT(device != NULL);
	RT_ASSERT(message != NULL);
	
	struct rt_spi_bus * gd32_spi_bus 		= (struct rt_spi_bus *)device->bus;
	struct gd32f4_spi *f4_spi 				= (struct gd32f4_spi *)gd32_spi_bus->parent.user_data;
	struct rt_spi_configuration * config 	= &device->config;
	struct gd32_spi_cs * gd32_spi_cs 		= device->parent.user_data;
	uint32_t spi_periph 					= f4_spi->spi_periph;
	uint8_t result = TRUE;
	/* take CS */
	if (message->cs_take) {
#ifdef RT_USING_PIN
		rt_pin_write(gd32_spi_cs->pin, PIN_LOW);
#else
		gpio_bit_reset(gd32_spi_cs->GPIOx, gd32_spi_cs->GPIO_Pin);
#endif
	}

	{
		rt_uint32_t retry_times;
		if (config->data_width <= 8){
			const rt_uint8_t * send_ptr 	= message->send_buf;
			rt_uint8_t * recv_ptr 			= message->recv_buf;
			rt_uint32_t size 				= message->length;
			while(size--){
				rt_uint8_t send_data = 0xFF;
				rt_uint8_t read_data = 0x00;
				if (send_ptr != RT_NULL){
					send_data = *send_ptr++;
				}
				// Todo: replace register read/write by gd32f4 lib
				//Wait until the transmit buffer is empty
				retry_times = 1000;
				while (RESET == spi_i2s_flag_get(spi_periph, SPI_FLAG_TBE)){
					   SPI_RETRY_PROCESS(NULL, retry_times, result);	
				}
				if (result != TRUE) {
					message->length = 0;
					goto _exit;
				}
				// Send the byte
				spi_i2s_data_transmit(spi_periph, send_data);
				//Wait until a data is received
				retry_times = 1000;
				while (RESET == spi_i2s_flag_get(spi_periph, SPI_FLAG_RBNE)){
					   SPI_RETRY_PROCESS(NULL, retry_times, result);	
				}
				if (result != TRUE) {
					message->length = 0;
					goto _exit;
				}
				// Get the received data
				read_data = spi_i2s_data_receive(spi_periph) & 0xFF;
				if (recv_ptr != RT_NULL){
					*recv_ptr++ = read_data;
				}
			}
		} else if(config->data_width <= 16) {
				const rt_uint16_t * send_ptr = message->send_buf;
				rt_uint16_t * recv_ptr = message->recv_buf;
				rt_uint32_t size = message->length;
				while(size--){
					rt_uint16_t send_data = 0xFF;
					rt_uint16_t read_data = 0x00;
					if (send_ptr != RT_NULL){
						send_data = *send_ptr++;
					}

					//Wait until the transmit buffer is empty
					retry_times = 1000;
					while (RESET == spi_i2s_flag_get(spi_periph, SPI_FLAG_TBE)){
							SPI_RETRY_PROCESS(NULL, retry_times, result);	
					}
					if (result != TRUE) {
						message->length = 0;
						goto _exit;
					}
					// Send the byte
					spi_i2s_data_transmit(spi_periph, send_data);

					//Wait until a data is received
					retry_times = 1000;
					while (RESET == spi_i2s_flag_get(spi_periph, SPI_FLAG_RBNE)){
							SPI_RETRY_PROCESS(NULL, retry_times, result);	
					}
					if (result != TRUE) {
						message->length = 0;
						goto _exit;
					}
					// Get the received data
					read_data = spi_i2s_data_receive(spi_periph);

					if (recv_ptr != RT_NULL){
						*recv_ptr++ = read_data;
					}
				}
		}
	}
	
_exit:
	/* release CS */
	if (message->cs_release){
#ifdef RT_USING_PIN
		rt_pin_write(gd32_spi_cs->pin, PIN_HIGH);
#else
	    gpio_bit_set(gd32_spi_cs->GPIOx, gd32_spi_cs->GPIO_Pin);
#endif
	}

	return message->length;
}

static struct rt_spi_ops gd32_spi_ops =
{
	.configure = gd32_spi_configure,
	.xfer 	   = gd32_spi_xfer,
};

static struct rt_spi_bus spi_bus[];

static const struct gd32f4_spi spis[] = {
#ifdef RT_USING_SPI0
{SPI0, RCU_SPI0, SPI0_BUS_NAME, &spi_bus[0]},
#endif

#ifdef RT_USING_SPI1
{SPI1, RCU_SPI1, SPI1_BUS_NAME, &spi_bus[1]},
#endif

#ifdef RT_USING_SPI2
{SPI2, RCU_SPI2, SPI2_BUS_NAME, &spi_bus[2]},
#endif

#ifdef RT_USING_SPI3
{SPI3, RCU_SPI3, SPI3_BUS_NAME, &spi_bus[3]},
#endif

#ifdef RT_USING_SPI4
{SPI4, RCU_SPI4, SPI4_BUS_NAME, &spi_bus[4]},
#endif
};

static struct rt_spi_bus spi_bus[ARR_LEN(spis)];

static int32_t gd32_spi_bus_init(void)
{
	for ( uint8_t i = 0; i < ARR_LEN(spis); i++){
		spis[i].spi_bus->parent.user_data = (void *)&spis[i];
		if ( RT_EOK != rt_spi_bus_register(spis[i].spi_bus, spis[i].bus_name, &gd32_spi_ops) ){
			return RT_ERROR;
		}
	}
	return RT_EOK;
}

/**
  * Attach the spi device to SPI bus, this function must be used after initialization.
  */
#ifdef RT_USING_PIN
rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, rt_base_t pin)
#else
rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, uint32_t cs_gpiox, uint32_t cs_gpio_pin)
#endif
{
	RT_ASSERT(bus_name != RT_NULL);
	RT_ASSERT(device_name != RT_NULL);
	
	struct rt_spi_device *spi_device = (struct rt_spi_device *)rt_malloc(sizeof(struct rt_spi_device));
	RT_ASSERT(spi_device != RT_NULL);	
	struct gd32_spi_cs *cs_pin       = (struct gd32_spi_cs *)rt_malloc(sizeof(struct gd32_spi_cs));
	RT_ASSERT(cs_pin != RT_NULL);
	
#ifdef RT_USING_PIN
	cs_pin->pin = pin;
    rt_pin_mode(pin, PIN_MODE_OUTPUT);
    rt_pin_write(pin, PIN_HIGH);
#else
	rcu_periph_enum cs_pin_periphs;
	/* initialize the cs pin && select the slave*/
	cs_pin->GPIOx 	  = cs_gpiox;
	cs_pin->GPIO_Pin  = cs_gpio_pin;
	
	if (cs_gpiox == GPIOA){
		cs_pin_periphs = RCU_GPIOA;
	} else if (cs_gpiox == GPIOB){
		cs_pin_periphs = RCU_GPIOB;
	} else if (cs_gpiox == GPIOC){
		cs_pin_periphs = RCU_GPIOC;
	} else if (cs_gpiox == GPIOD){
		cs_pin_periphs = RCU_GPIOD;
	} else if (cs_gpiox == GPIOE){
		cs_pin_periphs = RCU_GPIOE;
	} else {
		return RT_ERROR;
	}
	rcu_periph_clock_enable(cs_pin_periphs);
	gpio_mode_set(cs_gpiox, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, cs_gpio_pin);
	gpio_output_options_set(cs_gpiox, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, cs_gpio_pin);
	gpio_bit_set(cs_gpiox,cs_gpio_pin);
#endif
	/* attach the device to spi bus*/
	return rt_spi_bus_attach_device(spi_device, device_name, bus_name, (void *)cs_pin);
}

int32_t rt_hw_spi_init(void)
{
	return gd32_spi_bus_init();
}

INIT_BOARD_EXPORT(rt_hw_spi_init);

#endif

